package com.sam.mybatisplus.support.aspect;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.sam.common.utils.DateTool;
import com.sam.common.utils.ReflectionUtils;
import com.sam.mybatisplus.support.annotation.ConvertDateTimeZone;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @classname: RequestAop
 * @description: TODO
 * @Author: sam
 * @date: 2021/5/3 14:24
 */
@Slf4j
//@Aspect
//@Component
public class RequestAop {
    /**
     定义切点
     */
    @Pointcut("@annotation(com.sam.mybatisplus.support.annotation.Authorization)")
    public void executeService() {}

    @Pointcut("execution(public * com.sam.mybatisplus.controller..*.*(..))")
    public void methodPoint(){}

    private static String[] types = {"java.lang.Integer", "java.lang.Double",
            "java.lang.Float", "java.lang.Long", "java.lang.Short",
            "java.lang.Byte", "java.lang.Boolean", "java.lang.Char",
            "java.lang.String", "int", "double", "long", "short", "byte",
            "boolean", "char", "float"};

    /**
     环绕织入
     */
    @Around("methodPoint()")
    public Object proceed(ProceedingJoinPoint joinPoint) throws Throwable{
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //获取方法参数注解，返回二维数组是因为某些参数可能存在多个注解
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        // 获取所有参数值
        Object[] paramValues = joinPoint.getArgs();
        //获取方法参数名
        String[] paramNames = signature.getParameterNames();

        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (int j = 0; j < parameterAnnotations[i].length; j++) {
                Annotation annotation = parameterAnnotations[i][j];
                if (annotation instanceof ConvertDateTimeZone){
                    String paramName = paramNames[i];
                    Object paramValue = paramValues[i];
                    log.info("paramName:{},paramValue:{}",paramName,paramValue);
                }
            }
        }

        for (int i=0;i<paramNames.length;i++){
            Object paramValue = paramValues[i];
            if (Objects.isNull(paramValue)){
                continue;
            }
            recursion(paramValue,currTimeZoneId,tagerTimeZoneId);
        }
        return joinPoint.proceed();
    }
    String currTimeZoneId="Asia/Shanghai";
    String tagerTimeZoneId="Europe/London";

    /**
     * 递归处理时区
     */
    private void recursion(Object object,String currTimeZoneId,String tagerTimeZoneId){
        if (object==null){
            return;
        }
        if (!isPrimitive(object)) {
            Class<?> clazz = object.getClass();
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field field : declaredFields) {
                String type = field.getGenericType().toString(); // 获取属性的类型
                Object fieldValue = ReflectionUtils.getFieldValue(object, field.getName());
                if (ObjectUtil.isEmpty(fieldValue)){
                    continue;
                }

                if (type.contains("List")) {
                    //log.debug("处理返回值为list类型的数据");
                    List<Object> dataList = (List<Object>) fieldValue;
                    for (Object obj : dataList) {
                        recursion(obj, currTimeZoneId, tagerTimeZoneId);
                    }
                }else if (type.equals("class java.lang.String")){
                    // 处理返回单个对象类型，判断此对象中是否有添加需要处理时区的注解
                    ConvertDateTimeZone annotation = field.getAnnotation(ConvertDateTimeZone.class);
                    if (annotation==null){
                        continue;
                    }
                    // 格式
                    String pattern = annotation.pattern();
                    // 将此字段强制变为可修改
                    field.setAccessible(true);
                    if (fieldValue==null){
                        continue;
                    }
                    String dateTime = (String)fieldValue;
                    fieldValue=DateTool.timeZoneTransfer(dateTime,pattern,currTimeZoneId,tagerTimeZoneId);
                    ReflectionUtils.setFieldValue(object,field.getName(),fieldValue);
                }else if (type.equals("class java.util.Date")){
                    // 处理返回单个对象类型，判断此对象中是否有添加需要处理时区的注解
                    ConvertDateTimeZone annotation = field.getAnnotation(ConvertDateTimeZone.class);
                    if (annotation==null){
                        continue;
                    }
                    // 格式
                    String pattern = annotation.pattern();
                    // 将此字段强制变为可修改
                    field.setAccessible(true);
                    Date dateTime = (Date)fieldValue;
                    fieldValue=DateTool.timeZoneTransfer(dateTime,currTimeZoneId,tagerTimeZoneId);
                    ReflectionUtils.setFieldValue(fieldValue,field.getName(),fieldValue);
                }else {
                    recursion(fieldValue, currTimeZoneId, tagerTimeZoneId);
                }
            }
        }
    }


    /**
     * 判断一个对象是否是基本类型或基本类型的封装类型
     */
    private boolean isPrimitive(Object obj) {
        if (obj.getClass().isPrimitive() || obj.getClass() == String.class ) {
            return true;
        }
        return false;
        /*try {
            return ((Class<?>)obj.getClass().getField("TYPE").get(null)).isPrimitive();
        } catch (Exception e) {
            return false;
        }*/
    }

    // @Around("methodPoint()")
    public Object proceed2(ProceedingJoinPoint joinPoint) throws Throwable{
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
        Map<String, String[]> parameterMap = request.getParameterMap();
        StringBuffer sb = new StringBuffer();
        String requestId = IdUtil.simpleUUID();
        sb.append("\n【request_id】：").append(requestId);
        sb.append("\n【请求 URL】：").append(request.getRequestURL());
        sb.append("\n【请求 IP】：").append(getIp(request));
        sb.append("\n【请求类名】：").append(joinPoint.getSignature().getDeclaringTypeName());
        sb.append("【请求方法名】：").append(joinPoint.getSignature().getName());
        sb.append("\n【body】：").append(JSONUtil.toJsonStr(joinPoint.getArgs()));
        sb.append("\n【请求参数】：").append(JSONUtil.toJsonStr(parameterMap));
        log.info(sb.toString());

        Object[] args = joinPoint.getArgs();
        log.info("args：{}",args[0]);
        return joinPoint.proceed();
    }

    private static Map<String, Object> getFieldsName(ProceedingJoinPoint joinPoint){
        // 参数值
        Object[] args = joinPoint.getArgs();
        ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String[] parameterNames = pnd.getParameterNames(method);
        Map<String, Object> paramMap = new HashMap<>(32);
        for (int i = 0; i < parameterNames.length; i++) {
            paramMap.put(parameterNames[i], args[i]);
        }
        return paramMap;
    }

    private String getIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}
